Анализ новостных сообщений

Рассмотрим коллекцию новостных сообщений за первую половину 2017 года. Про каждое новостное сообщение известны:

  • его заголовок и текст
  • дата его публикации
  • событие, о котором это новостное сообщение написано
  • его рубрика
In [1]:
import pandas as pd

df = pd.read_csv('../data/news.csv')
df.head()
Out[1]:
text date event class
0 В ПЕТЕРБУРГЕ ПРОШЕЛ МИТИНГ ПРОТИВ ПЕРЕДАЧИ ИС... 2017-01-10 Передача РПЦ Исаакиевского собора Внутренняя политика РФ
1 Lenta.co, Москва, 14 января 2017 СИТУАЦИЯ С П... 2017-01-10 Передача РПЦ Исаакиевского собора Внутренняя политика РФ
2 Аргументы и Факты (aif.ru), Москва, 14 января... 2017-01-10 Передача РПЦ Исаакиевского собора Внутренняя политика РФ
3 Google Новости ТОП, Москва, 14 января 2017 АК... 2017-01-10 Передача РПЦ Исаакиевского собора Внутренняя политика РФ
4 Газета.Ru, Москва, 13 января 2017 В МОСКОВСКО... 2017-01-10 Передача РПЦ Исаакиевского собора Внутренняя политика РФ

Предварительный анализ коллекции

Средняя длина текстов

In [2]:
len_data = df.text.apply(len)
len_data.describe()
Out[2]:
count      1930.000000
mean       3798.322798
std        7865.936695
min          31.000000
25%        1215.250000
50%        1918.000000
75%        4044.000000
max      185698.000000
Name: text, dtype: float64

Количество текстов о разных событиях

In [ ]:
from bokeh.charts import Bar, output_notebook, show, hplot
import math
output_notebook()
In [3]:
counts = df.event.value_counts()

bar = Bar(counts,  width=1000, height = 600, legend = False)
bar.xaxis.major_label_orientation = math.pi/2-0.3
show(bar)
Loading BokehJS ...

Длины текстов (в символах)

In [4]:
from bokeh.charts import Histogram
hist = Histogram(len_data[len_data < 10000])
show(hist)

Токенизация

Используем регулярные выражения, чтобы разбить тексты на слова

In [5]:
import re
regex = re.compile("[А-Яа-я----]+")

def words_only(text, regex=regex):
    return " ".join(regex.findall(text))


df.text = df.text.str.lower()
df.text = df.text.apply(words_only)

Результат:

In [6]:
df.text.iloc[0]
Out[6]:
'в петербурге прошел митинг против передачи исаакиевского собора рпц в санкт-петербурге люди устроили акцию протеста против передачи исаакиевского собора в безвозмездное пользование рпц жители петербурга собрались на исаакиевской площади чтобы высказаться против передачи исаакиевского собора в безвозмездное пользование рпц передает тасс акция проходит в формате встречи с депутатами законодательного собрания города и не требует согласования с властями участники акции не используют какую-либо символику и плакаты а также мегафоны или средства звукоусиления по словам депутата алексея ковалева на исаакиевскую площадь пришло примерно тысяча человек перед участниками протеста выступили депутаты местного парламента борис вишневский и максим резник которые заявили о том что потребуют отмены решения смольного вишневский сообщил что акция будет проходить в виде встречи депутатов с избирателями закон санкт-петербурга предоставляет нам право встречаться с избирателями такую встречу мы и проведем расскажем как защищаем их интересы при передаче собора - сказал парламентарий в свою очередь директор музея исаакиевский собор николай буров проинформировал что собор в пятницу будет закрыт намного раньше в связи с акцией протеста он подчеркнул что необходимо избежать стычек между сторонниками передачи собора и ее противниками ранее стало известно что собор передадут в безвозмездное пользование на лет русской православной церкви в лице московского патриархата при этом он останется в собственности петербурга тем временем в петербурге продолжается сбор подписей под петицией об отмене данного решения под документом уже поставили подписи более тысяч человек комментарии другие интересные статьи - - - - - - - - -'

Самые частые слова

In [7]:
from nltk import FreqDist
n_types = []
n_tokens = []
tokens = []
fd = FreqDist()
for index, row in df.iterrows():
    tokens = row['text'].split()
    fd.update(tokens)
    n_types.append(len(fd))
    n_tokens.append(sum(fd.values()))
for i in fd.most_common(10):
    print(i)
('в', 43560)
('и', 25171)
('-', 24758)
('на', 19090)
('что', 13411)
('не', 11952)
('с', 10867)
('по', 8887)
('о', 5031)
('это', 4951)

Закон Ципфа

In [10]:
from bokeh.plotting import figure
freqs = list(fd.values())
freqs = sorted(freqs, reverse = True)


p = figure(plot_width=500, plot_height=300)

p.line(freqs[:300], range(300))
show(p) 

Закон Хипса

In [11]:
from bokeh.plotting import figure
freqs = list(fd.values())
freqs = sorted(freqs, reverse = True)


p = figure(plot_width=500, plot_height=300)

p.line(n_types, n_tokens)
show(p) 

Обработка текстов

Удаление стоп-слов

In [10]:
from nltk.corpus import stopwords
mystopwords = stopwords.words('russian') + ['это', 'наш' , 'тыс', 'млн', 'млрд', 'также',  'т', 'д', '-', '-']

print(mystopwords)
def  remove_stopwords(text, mystopwords = mystopwords):
    try:
        return " ".join([token for token in text.split() if not token in mystopwords])
    except:
        return ""
df.text = df.text.apply(remove_stopwords)   
['и', 'в', 'во', 'не', 'что', 'он', 'на', 'я', 'с', 'со', 'как', 'а', 'то', 'все', 'она', 'так', 'его', 'но', 'да', 'ты', 'к', 'у', 'же', 'вы', 'за', 'бы', 'по', 'только', 'ее', 'мне', 'было', 'вот', 'от', 'меня', 'еще', 'нет', 'о', 'из', 'ему', 'теперь', 'когда', 'даже', 'ну', 'вдруг', 'ли', 'если', 'уже', 'или', 'ни', 'быть', 'был', 'него', 'до', 'вас', 'нибудь', 'опять', 'уж', 'вам', 'ведь', 'там', 'потом', 'себя', 'ничего', 'ей', 'может', 'они', 'тут', 'где', 'есть', 'надо', 'ней', 'для', 'мы', 'тебя', 'их', 'чем', 'была', 'сам', 'чтоб', 'без', 'будто', 'чего', 'раз', 'тоже', 'себе', 'под', 'будет', 'ж', 'тогда', 'кто', 'этот', 'того', 'потому', 'этого', 'какой', 'совсем', 'ним', 'здесь', 'этом', 'один', 'почти', 'мой', 'тем', 'чтобы', 'нее', 'сейчас', 'были', 'куда', 'зачем', 'всех', 'никогда', 'можно', 'при', 'наконец', 'два', 'об', 'другой', 'хоть', 'после', 'над', 'больше', 'тот', 'через', 'эти', 'нас', 'про', 'всего', 'них', 'какая', 'много', 'разве', 'три', 'эту', 'моя', 'впрочем', 'хорошо', 'свою', 'этой', 'перед', 'иногда', 'лучше', 'чуть', 'том', 'нельзя', 'такой', 'им', 'более', 'всегда', 'конечно', 'всю', 'между', 'это', 'наш', 'тыс', 'млн', 'млрд', 'также', 'т', 'д', '-', '-']

Лемматизация

In [11]:
%%time 
from pymystem3 import Mystem

m = Mystem()
def lemmatize(text, mystem=m):
    try:
        return "".join(m.lemmatize(text)).strip()  
    except:
        return " "

df.text = df.text.apply(lemmatize)
CPU times: user 5.81 s, sys: 301 ms, total: 6.11 s
Wall time: 48.5 s

Удаление стоп-лемм

In [12]:
mystoplemmas = ['который','прошлый','сей', 'свой', 'наш', 'мочь']
def  remove_stoplemmas(text, mystoplemmas = mystoplemmas):
    try:
        return " ".join([token for token in text.split() if not token in mystoplemmas])
    except:
        return ""

df.text = df.text.apply(remove_stoplemmas)  

Самые частые леммы:

In [13]:
lemmata = []
for index, row in df.iterrows():
    lemmata += row['text'].split()
fd = FreqDist(lemmata)
for i in fd.most_common(10):
    print(i)
('россия', 5607)
('год', 4750)
('москва', 4614)
('человек', 4550)
('путин', 4346)
('президент', 4017)
('выборы', 2842)
('вопрос', 2654)
('время', 2261)
('российский', 2250)

Извлечение ключевых слов

Переезжаем из DataFrame в списки:

In [14]:
tokens_by_topic = []
for event in df.event.unique():
    tokens = []
    sample = df[df.event==event]
    for i in range(len(sample)):
        tokens += sample.text.iloc[i].split()
    tokens_by_topic.append(tokens)

Выберем событие, из текстов про которое будем извлекать ключевые слова:

In [15]:
event_id = 3

Извлекаем биграммы по разным мерам связности:

In [16]:
%%time 
import nltk
from nltk.collocations import *
N_best = 100 # число извлекаемых биграм

bigram_measures = nltk.collocations.BigramAssocMeasures() # класс для мер ассоциации биграм
finder = BigramCollocationFinder.from_words(tokens_by_topic[event_id]) # класс для хранения и извлечения биграм
finder.apply_freq_filter(3) # избавимся от биграм, которые встречаются реже трех раз
raw_freq_ranking = [' '.join(i) for i in finder.nbest(bigram_measures.raw_freq, N_best)] # выбираем топ-10 биграм по частоте 
tscore_ranking = [' '.join(i) for i in finder.nbest(bigram_measures.student_t, N_best)] # выбираем топ-100 биграм по каждой мере 
pmi_ranking =  [' '.join(i) for i in finder.nbest(bigram_measures.pmi, N_best)]
llr_ranking = [' '. join(i) for i in finder.nbest(bigram_measures.likelihood_ratio, N_best)]
chi2_ranking =  [' '.join(i) for i in finder.nbest(bigram_measures.chi_sq, N_best)]
CPU times: user 255 ms, sys: 4.91 ms, total: 260 ms
Wall time: 263 ms

Результаты:

In [17]:
rankings = pd.DataFrame({ 'chi2': chi2_ranking, 'llr':llr_ranking, 't-score' : tscore_ranking, 'pmi': pmi_ranking, 'raw_freq':raw_freq_ranking})
rankings = rankings[['raw_freq', 'pmi', 't-score', 'chi2', 'llr']]
rankings.head(10)
Out[17]:
raw_freq pmi t-score chi2 llr
0 дмитрий медведев анатолий афанасьевич дмитрий медведев алый парус дмитрий медведев
1 фонд дар артур саркисян фонд дар анатолий афанасьевич фонд дар
2 миллиард рубль атрибутика дореволюционный миллиард рубль арендный плата илья елисеев
3 илья елисеев афанасьевич младший илья елисеев артур саркисян миллиард рубль
4 алексей навальный взрывчатый токсичный алексей навальный атрибутика дореволюционный борьба коррупция
5 расследование фбк водный транспортный борьба коррупция афанасьевич младший скалистый берег
6 борьба коррупция военно-морской флот расследование фбк бадминтон увлечение квадратный метр
7 фонд борьба воспламеняющийся окислять фонд борьба безобидный комический доверенный лицо
8 фонд поддержка враг нападать фонд поддержка взрывчатый токсичный алексей навальный
9 доверенный лицо выращивание помидор доверенный лицо виноградарство субсидия москва март

Похожи ли списки биграм?

In [18]:
from scipy.stats import spearmanr
import seaborn as sns
%matplotlib inline
corr = spearmanr(rankings).correlation
sns.heatmap(corr, annot=True, xticklabels = list(rankings), yticklabels = list(rankings))
/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/scipy/stats/stats.py:253: RuntimeWarning: The input array could not be properly checked for nan values. nan values will be ignored.
  "values. nan values will be ignored.", RuntimeWarning)
Out[18]:
<matplotlib.axes._subplots.AxesSubplot at 0x116521ba8>

Используем TextRank для извлечения ключевых слов:

In [21]:
%%time
from gensim.summarization import keywords
text = ' '.join(tokens_by_topic[event_id])
kw = keywords(text)
Using TensorFlow backend.
CPU times: user 40.8 s, sys: 10.8 s, total: 51.6 s
Wall time: 54 s

Результаты:

In [22]:
rankings = pd.DataFrame({'Text Rank': kw.split('\n')})
rankings.head(10)
Out[22]:
Text Rank
0 дмитрии медведев фото александр
1 навальныи собирать
2 описание проходить расследование
3 премьер россия
4 такои
5 самыи схема
6 этот
7 яхта
8 усадьба
9 работа фбк насколько

RAKE

Для RAKE нужны сырые тексты со стоп-словами:

In [61]:
raw_df = pd.read_csv('../data/news.csv')
raw_df.text = raw_df.text.str.lower()
raw_df.text = raw_df.text.apply(words_only)
raw_df.text = raw_df.text.apply(lemmatize)
In [62]:
text = ' '.join(raw_df[raw_df.event == raw_df.event.unique()[3]].text.tolist())

Результаты RAKE:

In [63]:
import RAKE

Rake = RAKE.Rake('../data/stopwords.txt')
kp = [i[0] for i in Rake.run(text) if len(i[0].split())<3 and len(i[0].split())>1 and i[1]>1 and i[0] != '- -']
rankings = pd.DataFrame({'RAKE': kp})
rankings.head(10)
Out[63]:
RAKE
0 цертум-инвест цертум-инвест
1 коррумпированный-коррумпировать премьер-министр
2 кто-то фонд
3 интернет-магазин история
4 нынешний премьер-министр
5 где-то останавливаться
6 интерес премьер-министр
7 личный интернет-покупка
8 премьер-министр выбирать
9 интернет-покупка делать

Мера контрастности $tf-idf$

Извлекаем ключевые слова по $tf-idf$:

In [19]:
%%time 
from nltk.text import TextCollection 
tfidf_values = [] 
tfidf_ranking = []
corpus = TextCollection(tokens_by_topic) # класс для вычисления tf-idf
for i in set(tokens_by_topic[event_id]): # цикл по всем уникальным токенам в этом разделе
    tfidf_values.append([i, corpus.tf_idf(i, tokens_by_topic[event_id])]) # вычисляем tf-idf
for i in sorted(tfidf_values,key=lambda l:l[1], reverse=True)[:N_best]: # выбираем топ-100 по tf-idf
    tfidf_ranking.append(i[0])
CPU times: user 39.4 s, sys: 249 ms, total: 39.6 s
Wall time: 40.4 s

Результаты:

In [20]:
rankings = pd.DataFrame({'tf-idf': tfidf_ranking})
rankings.head(10)
Out[20]:
tf-idf
0 медведев
1 дар
2 елисеев
3 фбк
4 яхта
5 усадьба
6 виноградник
7 плес
8 агрокомплекс
9 усманов

Вычисление сходства и Gensim

Представление данных в Gensim словарем и корпусом:

In [48]:
from gensim.corpora import *
texts = [df.text.iloc[i].split() for i in range(len(df))]
dictionary = Dictionary(texts)
corpus = [dictionary.doc2bow(text) for text in texts]

Вычисление сходства по косинусной мере на векторах $tf-idf$:

In [49]:
%%time
from gensim.models import  *
tfidf = TfidfModel(corpus)
corpus_tfidf = tfidf[corpus]
CPU times: user 106 ms, sys: 2.8 ms, total: 109 ms
Wall time: 109 ms
In [54]:
from gensim import similarities

index = similarities.MatrixSimilarity(tfidf[corpus])
sims = index[corpus_tfidf]
In [55]:
from pylab import pcolor, show, colorbar, xticks, yticks
import seaborn as sns
import matplotlib.pyplot as plt
%matplotlib inline

plt.figure(figsize = (10,10))


sns.heatmap(data=sims, cmap = 'Spectral').set(xticklabels=[],yticklabels=[])

plt.title("Матрица близости")
plt.show()

Модели скрытых тем

Латентно-семантический анализ

In [50]:
%%time
lsi = lsimodel.LsiModel(corpus=corpus_tfidf, id2word=dictionary, num_topics=50)
CPU times: user 2.52 s, sys: 184 ms, total: 2.71 s
Wall time: 2.2 s
In [51]:
lsi.show_topics(5)
Out[51]:
[(0,
  '0.224*"путин" + 0.164*"трамп" + 0.148*"меркель" + 0.134*"президент" + 0.127*"выборы" + 0.118*"курортный" + 0.113*"навальный" + 0.112*"акция" + 0.110*"теракт" + 0.105*"сбор"'),
 (1,
  '-0.550*"курортный" + -0.467*"сбор" + -0.235*"законопроект" + -0.212*"эксперимент" + -0.159*"инфраструктура" + -0.148*"введение" + -0.129*"вносить" + -0.119*"крым" + -0.119*"край" + -0.112*"алтайский"'),
 (2,
  '0.355*"собор" + 0.266*"исаакиевский" + -0.247*"путин" + -0.233*"меркель" + 0.205*"передача" + 0.203*"акция" + 0.203*"рпц" + -0.170*"трамп" + 0.149*"навальный" + 0.134*"митинг"'),
 (3,
  '-0.316*"теракт" + 0.314*"собор" + -0.244*"барселона" + 0.235*"исаакиевский" + 0.182*"передача" + 0.178*"рпц" + -0.168*"лондон" + 0.139*"путин" + 0.138*"меркель" + -0.132*"чуркин"'),
 (4,
  '-0.464*"евтушенко" + -0.365*"чуркин" + -0.292*"поэт" + -0.233*"евгений" + -0.175*"виталий" + 0.163*"партия" + -0.159*"умирать" + -0.147*"оон" + 0.130*"теракт" + 0.121*"великобритания"')]

Как снижение размерности влияет на матрицу близости:

In [56]:
corpus_lsi = lsi[corpus]
index = similarities.MatrixSimilarity(lsi[corpus])
sims = index[corpus_lsi]
sims  = (sims + 1)/2.
plt.figure(figsize = (10,10))
sns.heatmap(data=sims, cmap = 'Spectral').set(xticklabels=[], yticklabels=[])
plt.title("Матрица близости")
plt.show()

Главные компоненты:

In [57]:
X = [0] * len(df)
Y = [0] * len(df)
for i in range(len(df)):
    vec = corpus[i]
    LSI_topics = (lsi[vec])
    try:
        for topic in LSI_topics:
            if topic[0] == 0:
                X[i] = topic[1]
            elif topic[0] == 1:
                Y[i] = topic[1]
    except:
        pass
vis_df = pd.DataFrame({'X': X, 'Y': Y, 'topic' : df.event})
In [58]:
sns.FacetGrid(vis_df, hue="topic", size = 10).map(plt.scatter, "X", "Y").add_legend()
Out[58]:
<seaborn.axisgrid.FacetGrid at 0x14e333b38>

Латентное размещение Дирихле

In [52]:
%%time
lda = ldamodel.LdaModel(corpus=corpus, id2word=dictionary, num_topics=20,
                        alpha='auto', eta='auto', iterations = 20, passes = 10)
CPU times: user 3min 7s, sys: 579 ms, total: 3min 8s
Wall time: 3min 8s
In [53]:
lda.show_topics(5)
Out[53]:
[(18,
  '0.011*"макрон" + 0.007*"франция" + 0.006*"тур" + 0.005*"ле" + 0.005*"пена" + 0.005*"победа" + 0.004*"первый" + 0.004*"выборы" + 0.004*"результат" + 0.004*"второй"'),
 (2,
  '0.019*"ракета" + 0.019*"запуск" + 0.016*"ступень" + 0.015*"компания" + 0.014*"первый" + 0.011*"повторный" + 0.010*"март" + 0.008*"спутник" + 0.007*"ракета-носитель" + 0.006*"орбита"'),
 (14,
  '0.020*"акция" + 0.020*"навальный" + 0.018*"митинг" + 0.016*"человек" + 0.014*"москва" + 0.009*"задерживать" + 0.009*"протест" + 0.008*"власть" + 0.007*"суд" + 0.007*"полиция"'),
 (5,
  '0.029*"москва" + 0.021*"человек" + 0.019*"ураган" + 0.009*"май" + 0.008*"погибший" + 0.008*"пострадать" + 0.008*"погибать" + 0.008*"столица" + 0.007*"сообщать" + 0.006*"тысяча"'),
 (1,
  '0.031*"путин" + 0.020*"президент" + 0.015*"россия" + 0.011*"вопрос" + 0.010*"меркель" + 0.008*"сша" + 0.008*"трамп" + 0.008*"встреча" + 0.008*"владимир" + 0.008*"российский"')]
In [69]:
import pyLDAvis.gensim as gensimvis
import pyLDAvis
vis_data = gensimvis.prepare(lda, corpus, dictionary)
pyLDAvis.display(vis_data)
Out[69]:
/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/json/encoder.py:199: DeprecationWarning: Interpreting naive datetime as local 2017-12-01 17:36:52.333998. Please add timezone info to timestamps.
  chunks = self.iterencode(o, _one_shot=True)
In [70]:
import plotly.offline as py
import plotly.graph_objs as go

py.init_notebook_mode()

def plot_difference(mdiff, title="", annotation=None):
    """
    Helper function for plot difference between models
    """
    annotation_html = None
    if annotation is not None:
        annotation_html = [
            [
                "+++ {}<br>--- {}".format(", ".join(int_tokens), ", ".join(diff_tokens)) 
                for (int_tokens, diff_tokens) in row
            ] 
            for row in annotation
        ]
        
    data = go.Heatmap(z=mdiff, colorscale='RdBu', text=annotation_html)
    layout = go.Layout(width=500, height=500, title=title, xaxis=dict(title="topic"), yaxis=dict(title="topic"))
    py.iplot(dict(data=[data], layout=layout))
/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/json/encoder.py:199: DeprecationWarning:

Interpreting naive datetime as local 2017-12-01 17:37:48.821297. Please add timezone info to timestamps.

In [71]:
mdiff, annotation = lda.diff(lda, distance='jaccard', num_words=50)
plot_difference(mdiff, title="Topic difference (one model) [jaccard distance]", annotation=annotation)
/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/json/encoder.py:199: DeprecationWarning:

Interpreting naive datetime as local 2017-12-01 17:38:04.726594. Please add timezone info to timestamps.

Иерархический процесс Дирихле

In [72]:
%%time
hdpmodel = HdpModel(corpus=corpus, id2word=dictionary)
CPU times: user 10 s, sys: 1.03 s, total: 11.1 s
Wall time: 10.4 s
/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/json/encoder.py:199: DeprecationWarning:

Interpreting naive datetime as local 2017-12-01 17:38:22.016208. Please add timezone info to timestamps.

In [73]:
hdpmodel.show_topics(5)
Out[73]:
[(0,
  '0.009*россия + 0.007*путин + 0.007*человек + 0.007*выборы + 0.007*москва + 0.007*год + 0.006*президент + 0.004*вопрос + 0.004*сказать + 0.004*время + 0.004*глава + 0.003*сообщать + 0.003*заявлять + 0.003*российский + 0.003*область + 0.003*день + 0.003*регион + 0.003*страна + 0.003*отмечать + 0.003*первый'),
 (1,
  '0.008*медведев + 0.007*россия + 0.006*москва + 0.006*фонд + 0.006*год + 0.006*человек + 0.005*партия + 0.004*выборы + 0.004*навальный + 0.003*один + 0.003*дмитрий + 0.003*вороненков + 0.003*получать + 0.003*самый + 0.003*становиться + 0.003*дар + 0.003*акция + 0.003*премьер-министр + 0.003*митинг + 0.002*президент'),
 (2,
  '0.005*москва + 0.005*теракт + 0.004*путин + 0.004*год + 0.004*трамп + 0.004*человек + 0.004*лондон + 0.004*россия + 0.003*президент + 0.003*март + 0.003*псаки + 0.003*собор + 0.003*сша + 0.003*заявлять + 0.003*сообщать + 0.002*российский + 0.002*расследование + 0.002*русский + 0.002*встреча + 0.002*связь'),
 (3,
  '0.007*расследование + 0.006*медведев + 0.004*навальный + 0.004*человек + 0.003*фбк + 0.003*теракт + 0.003*март + 0.002*фонд + 0.002*полиция + 0.002*москва + 0.002*власть + 0.002*парламент + 0.002*россия + 0.002*компания + 0.002*дмитрий + 0.002*заявлять + 0.002*один + 0.002*лондон + 0.002*сказать + 0.002*первый'),
 (4,
  '0.004*человек + 0.004*страна + 0.003*быть + 0.003*мир + 0.003*правительство + 0.003*время + 0.003*лондон + 0.003*тот + 0.003*теракт + 0.002*гражданин + 0.002*мэй + 0.002*один + 0.002*год + 0.002*сегодня + 0.002*британский + 0.002*свобода + 0.002*мы + 0.002*террорист + 0.002*новый + 0.002*великобритания')]
/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/json/encoder.py:199: DeprecationWarning:

Interpreting naive datetime as local 2017-12-01 17:38:38.720557. Please add timezone info to timestamps.

TSNE

In [59]:
from sklearn.manifold import TSNE
from sklearn.decomposition import TruncatedSVD
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import Normalizer
from sklearn.feature_extraction.text import *

vectors = TfidfVectorizer().fit_transform(df.text)
X_reduced = TruncatedSVD(n_components=5, random_state=0).fit_transform(vectors)
X_embedded = TSNE(n_components=2, perplexity=5, verbose=0).fit_transform(X_reduced)
In [60]:
vis_df = pd.DataFrame({'X': X_embedded[:, 0], 'Y': X_embedded[:, 1], 'topic' : df.event})
sns.FacetGrid(vis_df, hue="topic", size=10).map(plt.scatter, "X", "Y").add_legend()
Out[60]:
<seaborn.axisgrid.FacetGrid at 0x14e9425f8>